/**
 * README
 * 
 * Modificacion de la biblioteca jWave disponible en http://code.google.com/p/jwave/
 * Convertidos los tipos de datos a float para agilizar el tratamiendo de grandes arrays de datos
 * como son los que se tratan en analisis genomicos.
 * Los autores originales de las funciones y del código de la biblioteca cuando fue seleccionada en
 * Abril de 2012 eran:
 *        Christian Scheiblich
 *        Thomas Leduc
 *        sashi
 *        Pol Kennel
 *        Thomas Haider
 *        
 * Por supuesto, todo el merito, honores y demas menciones sobre esta adaptacion les corresponden a ellos
 * y nada mas que a ellos.
 * 
 * 
 * This is a modified version of jWave, available in http://code.google.com/p/jwave/
 * It has been replace the double datatype with float for performance reasons in very big arrays used in
 * bioinformatics like genomic analysis.
 * Original authors of functions and the library when it has been selected in 2012's April were:
 *        Christian Scheiblich
 *        Thomas Leduc
 *        sashi
 *        Pol Kennel
 *        Thomas Haider
 *        
 * Of course, all the merits and mentions about this adaptation have corresponded to them.
 * 
 */

package ev.maths.handlers;

/**
 * Este es un método para aplicar la transformada de wavelets
 * a arrays de un tamaño arbitrario.
 * Divide el tamaño array en factores que sean potencias de 2
 * de la siguiente forma: 42 = 2^5 + 2^3 + 2^1 = 32 + 8 + 2.
 * See: http://en.wikipedia.org/wiki/Ancient_Egyptian_multiplication
 * 
 * @date 01.08.2012
 * @author Enrique Vázquez de Luis
 */
public class AncientEgyptianDecomposition extends WaveletTransform {

  /**
   * La transformada que se va a utilizar
   */
  protected WaveletTransform _waveTransform;

  /**
   * El contructor que toma la wavelet a aplicar.
   * 
   * @date 01.08.2012
   * @author Enrique Vázquez de Luis
   */
  public AncientEgyptianDecomposition( WaveletTransform waveTransform ) {
    _waveTransform = waveTransform;
  } 

  /**
   * Este método convierte un entero en su descomposición
   * en factores potencias de 2.
   * 
   * @date 01.08.2012
   * @author Enrique Vázquez de Luis
   * @param number Entero positivo
   * @return Un array de enteros con todos los sumandos
   */
  public int[ ] convertInteger2AncientEgyptianMultiplipliers( int number ) {

    if( number < 1 )
      return null;

    int power = getExponent( (double)number );

    int[ ] tmpArr = new int[ power + 1 ];

    int pos = 0;
    double current = (double)number;
    while( current >= 1. ) {

      power = getExponent( current );
      tmpArr[ pos ] = power;
      current = current - scalb( 1., power ); 
      pos++;

    } // while

    int[ ] ancientEgyptianMultipliers = new int[ pos ]; 
    for( int c = 0; c < pos; c++ )
      ancientEgyptianMultipliers[ c ] = tmpArr[ c ];

    return ancientEgyptianMultipliers;

  } // convertInteger2AncientEgyptianMultiplipliers

  /**
   * Este método pasa una lista de potencias de 2 en el número que forman
   * 
   * @date 01.08.2012
   * @author Enrique Vázquez de Luis
   * @param ancientEgyptianMultipliers Un array con todos los multiplicadores
   * @return El entero resultante
   */
  public int convertAncientEgyptianMultiplipliers2Integer(
      int[ ] ancientEgyptianMultipliers ) {

    int number = 0;

    int noOfAncientEgyptianMultipliers = ancientEgyptianMultipliers.length;
    for( int m = 0; m < noOfAncientEgyptianMultipliers; m++ ) {

      int ancientEgyptianMultiplier = ancientEgyptianMultipliers[ m ];

      number += (int)scalb( 1., ancientEgyptianMultiplier ); // 1. * 2^p

    } // m

    return number;

  } // convertAncientEgyptianMultiplipliers2Integer

  /**
   * Este método descompone el array con los datos de entrada en subarrays
   * dentro del mismo, de longitud potencia de 2 y les aplica a cada uno
   * la transformada de wavelet.
   * 
   * @date 01.08.2012
   * @author Enrique Vázquez de Luis
   * @see math.transform.jwave.handlers.BasicTransform#forward(double[])
   */
  @Override
  public float[ ] forwardWavelet( float[ ] arrTime ) {

    float[ ] arrHilb = new float[ arrTime.length ];

    int[ ] ancientEgyptianMultipliers = convertInteger2AncientEgyptianMultiplipliers( arrTime.length );

    int offSet = 0;
    for( int m = 0; m < ancientEgyptianMultipliers.length; m++ ) {

      int ancientEgyptianMultiplier = ancientEgyptianMultipliers[ m ];

      int arrTimeSubLength = (int)scalb( 1., ancientEgyptianMultiplier );

      float[ ] arrTimeSub = new float[ arrTimeSubLength ];
      for( int i = 0; i < arrTimeSub.length; i++ )
        arrTimeSub[ i ] = arrTime[ i + offSet ];

      float[ ] arrHilbSub = _waveTransform.forward( arrTimeSub );

      for( int i = 0; i < arrHilbSub.length; i++ )
        arrHilb[ i + offSet ] = arrHilbSub[ i ];

      offSet += arrHilbSub.length;

    } // m - no of sub transforms

    return arrHilb;
  } // forward

  /**
   * Este método descompone coge un array de Hilbert al que se le ha realizado
   * la transformada en trozos de tamaño potencia de 2 y realiza la transformada
   * inversa.
   * 
   * @date 01.08.2012
   * @author Enrique Vázquez de Luis
   * @see math.transform.jwave.handlers.BasicTransform#reverse(double[])
   */
  @Override
  public float[ ] reverseWavelet( float[ ] arrHilb ) {

    float[ ] arrTime = new float[ arrHilb.length ];

    int[ ] ancientEgyptianMultipliers = convertInteger2AncientEgyptianMultiplipliers( arrHilb.length );

    int offSet = 0;
    for( int m = 0; m < ancientEgyptianMultipliers.length; m++ ) {

      int ancientEgyptianMultiplier = ancientEgyptianMultipliers[ m ];

      int arrHilbSubLength = (int)scalb( 1., ancientEgyptianMultiplier );

      float[ ] arrHilbSub = new float[ arrHilbSubLength ];
      for( int i = 0; i < arrHilbSub.length; i++ )
        arrHilbSub[ i ] = arrHilb[ i + offSet ];

      float[ ] arrTimeSub = _waveTransform.reverse( arrHilbSub );

      for( int i = 0; i < arrTimeSub.length; i++ )
        arrTime[ i + offSet ] = arrTimeSub[ i ];

      offSet += arrHilbSub.length;

    } // m - no of sub transforms

    return arrTime;
  } // reverse

  /**
   * Este método descompone el array con los datos de entrada en subarrays
   * dentro del mismo, de longitud potencia de 2 y les aplica a cada uno
   * la transformada de wavelet hasta un nivel determinado.
   * 
   * @date 01.08.2012
   * @author Enrique Vázquez de Luis
   * @param toLevel El nivel hasta el que se debe realizar la transformada
   * @see math.transform.jwave.handlers.BasicTransform#forward(double[], int)
   */
  @Override
  public float[ ] forwardWavelet( float[ ] arrTime, int toLevel ) {

    float[ ] arrHilb = new float[ arrTime.length ];

    int[ ] ancientEgyptianMultipliers = convertInteger2AncientEgyptianMultiplipliers( arrTime.length );

    int offSet = 0;
    for( int m = 0; m < ancientEgyptianMultipliers.length; m++ ) {

      int ancientEgyptianMultiplier = ancientEgyptianMultipliers[ m ];

      int arrTimeSubLength = (int)scalb( 1., ancientEgyptianMultiplier );

      float[ ] arrTimeSub = new float[ arrTimeSubLength ];
      for( int i = 0; i < arrTimeSub.length; i++ )
        arrTimeSub[ i ] = arrTime[ i + offSet ];

      float[ ] arrHilbSub = _waveTransform.forwardWavelet( arrTimeSub, toLevel );

      for( int i = 0; i < arrHilbSub.length; i++ )
        arrHilb[ i + offSet ] = arrHilbSub[ i ];

      offSet += arrHilbSub.length;

    } 

    return arrHilb;
  } // forward

  /**
   * Este método descompone coge un array de Hilbert al que se le ha realizado
   * la transformada en trozos de tamaño potencia de 2 y realiza la transformada
   * inversa desde un nivel determinado.
   * 
   * @date 01.08.2012
   * @author Enrique Vázquez de Luis
   * @param fromLevel El nivel desde el que se parte para hacer la transformada inversa
   * @see math.transform.jwave.handlers.BasicTransform#reverse(double[], int)
   */
  @Override
  public float[ ] reverseWavelet( float[ ] arrHilb, int fromLevel ) {

    float[ ] arrTime = new float[ arrHilb.length ];

    int[ ] ancientEgyptianMultipliers = convertInteger2AncientEgyptianMultiplipliers( arrHilb.length );

    int offSet = 0;
    for( int m = 0; m < ancientEgyptianMultipliers.length; m++ ) {

      int ancientEgyptianMultiplier = ancientEgyptianMultipliers[ m ];

      int arrHilbSubLength = (int)scalb( 1., ancientEgyptianMultiplier );

      float[ ] arrHilbSub = new float[ arrHilbSubLength ];
      for( int i = 0; i < arrHilbSub.length; i++ )
        arrHilbSub[ i ] = arrHilb[ i + offSet ];

      float[ ] arrTimeSub = _waveTransform.reverseWavelet( arrHilbSub, fromLevel );

      for( int i = 0; i < arrTimeSub.length; i++ )
        arrTime[ i + offSet ] = arrTimeSub[ i ];

      offSet += arrHilbSub.length;

    } 

    return arrTime;
  } // reverse

  /**
   * Reemplazo de scalb
   * 
   * @date 01.08.2012
   * @author Enrique Vázquez de Luis
   * @param f
   * @param scaleFactor
   * @return f times 2^(scaleFactor)
   */
  public static double scalb( double f, int scaleFactor ) {
    return f * Math.pow( 2, scaleFactor );
  } // scalb

  /**
   * reemplazo de getExponent
   * 
   * @date 01.08.2012
   * @author Enrique Vázquez de Luis
   * @param f
   * @return return p of 2^p <= f < 2^(p+1)
   */
  public static int getExponent( double f ) {
    return (int)( Math.log( f ) / Math.log( 2 ) );
  } // getExponent

} // class
